开发步骤
- 在 SE 阶段,在项目路径下新建 libs 文件夹专门存放 jar 包
- 编写代码
1 2 3 4 5 6 7 8 9 10
| 所设计到的 API : 1. Connection 接口,代表连接 2. DriverManger,驱动管理类 getConnection(xx),获取链接 3. Statement executeUpdate(sql),执行更新类的SQL executeQuery(sql),执行查询,返回 ResultSet 类型 4. ResultSet next(),判断是否还有数据 getXXX(),获取数据
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| package com.itguigu.mysql;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
import org.junit.Test;
public class TestJDBC {
@Test public void test1() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, user, password); String sql = "insert into xxx values(null, '1', '2')"; Statement statement = connection.createStatement(); int rows = statement.executeUpdate(sql); System.out.println("影响行数:" + rows); statement.close(); connection.close(); } @Test public void test2() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, user, password); String sql = "select * from xxx"; Statement statement = connection.createStatement(); ResultSet executeQuery = statement.executeQuery(sql); while (executeQuery.next()) { int id = executeQuery.getInt("id"); String name = executeQuery.getString("name"); String age = executeQuery.getString(3); Object salary = executeQuery.getObject(4); } executeQuery.close(); statement.close(); connection.close(); } }
|
使用 Statement 存在的问题有:1. 需要拼接 SQL,2. 不能防止 SQL 注入,3. 不能插入 blob 等二进制类型数据。
PreparedStatement
PreparedStatement 能解决上面 Statement 的问题。Statement 是 PreparedStatement 的父接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.itguigu.mysql;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
import org.junit.Test;
public class TestPrepare { @Test public void test1() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, user, password); String sql = "insert into xxx(name, age, salary) values(?, ?, ?)"; PreparedStatement prepareStatement = connection.prepareStatement(sql); prepareStatement.setObject(1, "zhangsan"); prepareStatement.setObject(2, 20); prepareStatement.setObject(3, 30000); int len = prepareStatement.executeUpdate(); prepareStatement.close(); connection.close(); } }
|
获取自增主键的值
Statement 中有一个常量值 RETURN_GENERATED_KEYS, 可在创建 PreparedStatement 的设置,让其返回自增主键的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package com.itguigu.mysql;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
import org.junit.Test;
public class TestPrepare { @Test public void test1() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, user, password); String sql = "insert into xxx(name, age, salary) values(?, ?, ?)"; PreparedStatement prepareStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); prepareStatement.setObject(1, "zhangsan"); prepareStatement.setObject(2, 20); prepareStatement.setObject(3, 30000); int len = prepareStatement.executeUpdate(); ResultSet generatedKeys = prepareStatement.getGeneratedKeys(); if(generatedKeys.next()) { System.out.println(generatedKeys.getObject(1)); } generatedKeys.close(); prepareStatement.close(); connection.close(); } }
|
批处理
- addBatch, 将 SQL 添加到批处理命令中,进行缓存。如果缓冲区满了之后会自动执行。
- excuteBatch, 执行批处理语句
注意:MySQL 中是默认不进行批处理的(即速度很慢),需要在连接的时候加上 rewriteBatchedStatements = true。另外使用 insert 插入的时候应该使用 values 而不是 value,不然也会导致批处理速度变慢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.itguigu.mysql;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
import org.junit.Test;
public class TestPrepare { @Test public void test1() throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test?rewriteBatchedStatements = true"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, user, password); String sql = "insert into xxx(name, age, salary) values(?, ?, ?)"; PreparedStatement prepareStatement = connection.prepareStatement(sql); for (int i = 0; i < 1000; i++) { prepareStatement.setObject(1, "value" + i); prepareStatement.setObject(2, 20 + i); prepareStatement.setObject(3, 30000 + i); prepareStatement.addBatch(); } int[] executeBatch = prepareStatement.executeBatch(); prepareStatement.close(); connection.close(); } }
|
事务
MySQL 是自动提交事务的,执行一句,提交一句。使用事务时需要注意:
- 同一个事务必须保证使用的是同一个 Connection 对象
- 在执行 SQL 之前,设置自动提交为 false
- 如果没问题则进行 commit,有问题则 rollback。
- 进行连接关闭前,需要将自动提交还原为以前的 true。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| package com.itguigu.mysql;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;
import org.junit.Test;
public class TestPrepare { public static void main(String[] args) { Connection connection = null; try { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "123456"; connection = DriverManager.getConnection(url, user, password); connection.setAutoCommit(false); insert(connection); update(connection); connection.commit(); } catch (Exception e) { try { connection.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally { try { connection.setAutoCommit(true); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void insert(Connection connection) throws Exception { String sql = "insert into xxx(name, age, salary) values(?, ?, ?)"; PreparedStatement prepareStatement = connection.prepareStatement(sql); prepareStatement.setObject(1, "1"); prepareStatement.setObject(2, "2"); prepareStatement.setObject(3, "3"); prepareStatement.executeUpdate(); prepareStatement.close(); } public static void update(Connection connection) throws Exception { String sql = "update xxx set name = ? where id =?"; PreparedStatement prepareStatement = connection.prepareStatement(sql); prepareStatement.setObject(1, "zhangsan"); prepareStatement.setObject(2, "2"); prepareStatement.executeUpdate(); prepareStatement.close(); } }
|
连接池 JDBCUtils
使用阿里云德鲁伊(Druid)。步骤如下:
引入 jar 包
增加配置文件
在 src 下新建 druid.properties 文件,输入以下配置
1 2 3 4 5 6 7
| url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true username=root password=123456 driverClassName=com.mysql.jdbc.Driver initialSize=10 maxActive=20 maxWait=1000
|
创建连接池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.itguigu.mysql;
import java.sql.Connection; import java.sql.SQLException; import java.util.Properties;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class JDBCUtils { private static DataSource ds; private static ThreadLocal<Connection> local; static { local = new ThreadLocal<>(); Properties properties = new Properties(); try { properties.load(JDBCUtils.class.getResourceAsStream("druid.properties")); ds = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e1) { e1.printStackTrace(); } }
public static Connection getConnection() throws SQLException { Connection connection = local.get(); if (connection==null) { connection = ds.getConnection(); local.set(connection); } return connection; } public static void Close() throws SQLException { Connection connection = local.get(); if (connection!=null) { local.remove(); connection.setAutoCommit(true); connection.close(); } } }
|
使用 JDBCUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.itguigu.mysql;
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException;
public class TestJDBCUtils { public static void main(String[] args) throws SQLException { Connection connection = JDBCUtils.getConnection(); String sql = "insert into xxx(name, age, salary) values(?, ?, ?)"; PreparedStatement prepareStatement = connection.prepareStatement(sql); prepareStatement.setObject(1, "zhangsan"); prepareStatement.setObject(2, 20); prepareStatement.setObject(3, 30000); prepareStatement.close(); JDBCUtils.Close(); } }
|
抽取 BasicDAO
在现实业务中,很多业务代码都是重复的,例如员工的增删改查,部门的增删改查等,我们可以使用反射等技术,将通用的代码逻辑进行封装。执行查询后的 ResultSet 可以获取到结果的元对象,可以通过元对象获取到结果集的列数(即 JavaBean 有多少个属性,知道有多少后我们就可以给每一个属性赋值),或者根据位置获取列名称 (获取每一列的名称)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| package com.itguigu.dao;
import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.util.ArrayList;
import com.itguigu.mysql.JDBCUtils;
public class BasicDAO {
public int update(String sql, Object...args) throws Exception { Connection connection = JDBCUtils.getConnection(); PreparedStatement prepareStatement = connection.prepareStatement(sql); if (args!=null && args.length > 0) { for (int i = 0; i < args.length; i++) { prepareStatement.setObject(i+1, args[i]); } } int executeUpdate = prepareStatement.executeUpdate(); prepareStatement.close(); JDBCUtils.Close(); return executeUpdate; }
public <T> ArrayList<T> getAllData(Class<T> clazz, String sql, Object...args) throws Exception{ Connection connection = JDBCUtils.getConnection(); PreparedStatement prepareStatement = connection.prepareStatement(sql); if (args!=null && args.length > 0) { for (int i = 0; i < args.length; i++) { prepareStatement.setObject(i+1, args[i]); } } ResultSet resultSet = prepareStatement.executeQuery(sql); ArrayList<T> resultList = new ArrayList<>(); ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); T instance = clazz.newInstance(); for (int i = 0; i < columnCount; i++) { String columnNmae = metaData.getColumnLabel(i+1); Field field = clazz.getDeclaredField(columnNmae); field.setAccessible(true); field.set(instance, resultSet.getObject(i+1)); } resultList.add(instance); prepareStatement.close(); JDBCUtils.Close(); return resultList; } }
|
使用 BasicDAO
新建 Deparment Java Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.itguigu.bean;
public class Department { private String name; private String desc; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } @Override public String toString() { return "Department [name=" + name + ", desc=" + desc + "]"; } public Department(String name, String desc) { super(); this.name = name; this.desc = desc; } public Department() { super(); } }
|
新建 DepartmentDAO 接口
1 2 3 4 5 6 7 8 9 10 11
| package com.itguigu.department;
import java.util.ArrayList;
import com.itguigu.bean.Department;
interface DepartmentDAO { void add(Department department); ArrayList<Department> getAll(); void delete(String name); }
|
新建 DepartmentDAOImpl 继承自 BasicDAO 和实现 DepartmentDAO 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.itguigu.department;
import java.util.ArrayList;
import com.itguigu.bean.Department; import com.itguigu.dao.BasicDAO;
public class DepartmentDAOImpl extends BasicDAO implements DepartmentDAO{
@Override public void add(Department department) { String sql = "insert into department(name, desc) values(?, ?)"; try { update(sql, department.getName(), department.getDesc()); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } }
@Override public ArrayList<Department> getAll() { String sql = "select * from department"; try { ArrayList<Department> allData = getAllData(Department.class, sql); return allData; } catch (Exception e) { throw new RuntimeException(e.getMessage()); } }
@Override public void delete(String name) { String sql = "delete from department where name = ?"; try { update(sql, name); } catch (Exception e) { throw new RuntimeException(e.getMessage()); } } }
|
新建测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.itguigu.department;
import java.util.ArrayList;
import org.junit.Test;
import com.itguigu.bean.Department;
public class TestDeparmentDAOImpl { @Test public void testAdd() { Department department = new Department("运行", "运行部"); DepartmentDAOImpl departmentDAOImpl = new DepartmentDAOImpl(); departmentDAOImpl.add(department); } @Test public void testGetAll() { DepartmentDAOImpl departmentDAOImpl = new DepartmentDAOImpl(); ArrayList<Department> allData = departmentDAOImpl.getAll(); for (Department department : allData) { System.out.println(department); } } @Test public void testDel() { DepartmentDAOImpl departmentDAOImpl = new DepartmentDAOImpl(); departmentDAOImpl.delete("运行部"); } }
|
项目结构如图所示
注意⚠️:在书写 java bean 的时候,属性的类型必须使用包装类,因为数据库中的所有类型,包括整数等类型都可能是 null,而在 java 中只有引用数据类型才能赋值为 null,基本数据类型是不可以的,所以在书写 java bean 的时候,属性的类型必须使用包装类。
Apache DBUtils
Apache DBUtils 包中提供了主要的 DBUtils 类和 QueryRunner 类,DBUtils 的功能主要是对连接的关闭操作。QueryRunner 主要提供了 增删改查,事务共用 connection 等方法。我们使用 Apache DBUtils 重新实现 BasicDao。学习 Apache DBUtils 的目的在于理解数据如何从数据库映射到一个对象,一个数据对象如果转换成一条数据库数据,理解里面的工作原理 ——— 反射。
Apache DBUtils 中提供了结果集处理器的接口 ResultSetHandler,在调用 QueryRunner 的 query 方法时,可以传入对应的结果集处理实现类。常见的实现类有
- BeanListHandler,结果集是一个 List。
- BeanHandler,结果是一个 Java Bean 对象。
- MapHandler,结果是一行队列,但不是 Java Bean 对象
- MapListHandler,结果是多行多列,但不是 Java Bean 对象
- ScalarHandler,单个值的封装对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package com.itguigu.dao;
import java.sql.Connection; import java.util.List; import java.util.Map;
import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.MapListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler;
public class BasicDAO2 { private QueryRunner queryRunner; public int update(String sql, Object...args) throws Exception { int update = queryRunner.update(sql, args); return update; } public int update(Connection connection, String sql, Object...args) throws Exception { int update = queryRunner.update(connection, sql, args); return update; } public <T> List<T> getList(Class<T> clazz, String sql, Object...args) throws Exception{ return queryRunner.query(sql, new BeanListHandler<T>(clazz), args); } public <T> T getBean(Class<T> clazz, String sql, Object...args) throws Exception{ return queryRunner.query(sql, new BeanHandler<T>(clazz), args); } public Object queryObject(String sql, Object...args) throws Exception { return queryRunner.query(sql, new ScalarHandler<>(), args); } public List<Map<String, Object>> queryListMap(String sql, Object...args) throws Exception { return queryRunner.query(sql, new MapListHandler(), args); } public Map<String, Object> queryMap(String sql, Object...args) throws Exception { return queryRunner.query(sql, new MapHandler(), args); } }
|